﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;

using Noris.Schedule.Support;
using Noris.Schedule.Support.Services;
using Noris.Schedule.Support.Data;

namespace Noris.Schedule.UI.Components
{
	#region CLASS GridNodeRowCls : data jednoho nodu v síti Tree, základ pro konstrukci stromu s prvky s více Parenty i více Childs
	/// <summary>
	/// GridNodeRowCls : data jednoho nodu v síti Tree, základ pro konstrukci stromu s prvky s více Parenty i více Childs.
	/// Protože se jeden datový prvek může vyskytovat na více řádcích grafu, a může mít různě zavřené / otevřené nody, 
	/// musí existovat více instancí této třídy GridNodeRowCls = pro každý výskyt jedna.
	/// Rozdíl mezi třídou GridDataRowCls a GridNodeRowCls:
	/// Třída GridDataRowCls rozšiřuje data IDataRow o paměť elementů (a jejich časové přepočty).
	/// Třída GridNodeRowCls představuje jednu vizuální pozici datového řádku (kterých může být více) ve strumu struktury.
	/// </summary>
	internal class GridNodeRowCls
	{
		#region KONSTRUKCE
		private GridNodeRowCls(int id, GridDataRowCls dataRow, GridNodeRowCls parent, bool subNodesExists)
		{
			this.Id = id;
            this.DataRow = dataRow;
			this.Parent = parent;
			this.Childs = new List<GridNodeRowCls>();
			this.SubNodesExists = subNodesExists;
			this.SubNodesLoaded = false;
			this.SubNodesVisible = false;
		}
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "{GID=" + this.GId.ToString() +
				"; ID=" + this.Id.ToString() +
				"; Parent" + (this.Parent == null ? ": none" : "ID=" + this.Parent.Id.ToString()) + 
                "; ChildCount=" + this.Childs.Count.ToString() + "}";
		}
		#endregion
		#region TVORBA OBJEKTU
        internal static GridNodeRowCls Create(int id, GridDataRowCls dataRow, GridNodeRowCls parent)
		{
            return new GridNodeRowCls(id, dataRow, parent, dataRow.RowItem.HasSubNodes);
		}
        internal static GridNodeRowCls Create(int id, GridDataRowCls dataRow)
		{
            return new GridNodeRowCls(id, dataRow, null, dataRow.RowItem.HasSubNodes);
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// ID tohoto záznamu = jednoznačný klíč ke konkrétnímu řádku. Po dobu živata grafu 
		/// je neměnné bez ohledu na aktuální viditelnost / neviditelnost řádku, 
		/// a je unikátní bez ohledu na vícenásobný výskyt řádků jednoho GIDu.
		/// ID začínají 1. 0 je vyhrazena jako neexistující ID (viz ParentId).
		/// </summary>
		internal int Id { get; private set; }
        /// <summary>
        /// Řádek s daty tohoto nodu. Node je vizuální uzel stromu, Data jsou řádek z datového zdroje. 
        /// Jeden řádek s Daty může být zobrazen na více Nodech. 
        /// Jeden Node má pouze jeden Datový řádek.
        /// </summary>
        internal GridDataRowCls DataRow { get; private set; }
		/// <summary>
		/// Node záznamu Parent, vede na konkrétní řádek Parenta (když by zde byl GID, tak nelze určit který řádek tohoto GIDu to je).
		/// Jeden GID může být zobrazen na více řádkách.
		/// Pokud je zde 0, pak tento řádek nemá Parenta, a je zobrazen v prvním Listu (pak je IsTopLevelRow == true).
		/// </summary>
        internal GridNodeRowCls Parent { get; private set; }
        /// <summary>
        /// Moje děti (jeden řádek může mít více dětí).
        /// Obsahuje ID položek Child, nikoli jejich GID.
        /// </summary>
        internal List<GridNodeRowCls> Childs { get; private set; }
        /// <summary>
        /// Tento řádek má nějaké subnody (bez ohledu na to, zda jsou nebo nejsou načteny a rozbaleny)
        /// </summary>
        internal bool SubNodesExists { get; set; }
        /// <summary>
        /// Tento řádek má své subnodes již načteny do tohoto seznamu
        /// </summary>
        internal bool SubNodesLoaded { get; set; }
        /// <summary>
        /// Tento řádek má v současné době své subnodes viditelné (rozbalené)
        /// </summary>
        internal bool SubNodesVisible { get; set; }
        /// <summary>
        /// GID tohoto záznamu = řádek.
        /// V záznamu této třídy nejsou uložena přímo data řádku, ale přes GID jsou snadno k dohledání.
        /// </summary>
        internal GID GId { get { return this.DataRow.GId; } }
        /// <summary>
        /// Příznak true, pokud tento node má parenta.
        /// </summary>
        internal bool HasParent { get { return (this.Parent != null); } }
		/// <summary>
		/// Příznak, zda tento řádek je / není v TopLevel seznamu řádků.
		/// true = řádek má být vypsán v seznamu první úrovně / false = ne, vždy je jen jako Child pro nějaký jiný řádek.
		/// Řádek je v TopLevel tehdy, když z datového zdroje přijde bez zadaného parenta.
		/// </summary>
		internal bool IsTopLevelRow { get { return this.Parent == null; } }
		/// <summary>
		/// Příznak, že tento řádek obsahuje nějaký (alespoň jeden) odkaz na Childs řádky
		/// </summary>
		internal bool HasChilds { get { return this.Childs != null && this.Childs.Count > 0; } }
        /// <summary>
        /// Metoda zajistí invalidaci (zahození) všech Child nodů tohoto nodu.
        /// V evidenci zůstává fakt, že tento node má Childs, ale dosud nejsou načteny.
        /// </summary>
        internal void InvalidateChilds()
        {
            if (this.Childs != null)
            {
                foreach (GridNodeRowCls subNode in this.Childs)
                {
                    subNode.DataRow.ChartElementClear();
                }
                this.SubNodesLoaded = false;
                this.Childs.Clear();
            }
        }
		/// <summary>
		/// Obsahuje true, pokud tento node je viditelný z hlediska otevření/zavření nodů.
		/// Tento node je viditelný, když jeho parent je viditelný a otevřený.
		/// Pokud node nemá parenta, pak se bere za viditelný (tím je zřejmé, že se neuplatňuje řádkový filtr, ale jen otevření/zavření nodů).
		/// </summary>
		internal bool ThisNodeVisible
		{
			get
			{
				if (this.Parent == null) return true;         // Parent není => já jsem TopLevel => jsem vidět.

				// Parent existuje => já jsem vidět, když můj parent je vidět a když je otevřený:
				return (this.Parent.ThisNodeVisible && this.Parent.SubNodesVisible);
			}
		}
		#endregion
        #region FILTER and SORT PROPERTY
        /// <summary>
        /// Tento řádek může být zobrazen v seznamu, protože vyhovuje filtru on sám, nebo jeho parent, nebo některý z jeho Childs.
        /// </summary>
        internal bool NodeIsVisible { get { return (this.DataRowIsVisibleByFilter || this.NodeIsVisibleFromParent || this.NodeIsVisibleFromChild); } }

        /// <summary>
        /// Tento řádek sám je vybrán filtrem jako viditelný (osobně vyhovuje filtru) (je uvedeno na DataRow.RowIsVisibleByFilter).
        /// </summary>
        internal bool DataRowIsVisibleByFilter { get { return this.DataRow.RowIsVisibleByFilter; } }
        /// <summary>
        /// Tento řádek má být zobrazen, protože jeho Parent vyhovuje filtru, anebo našeho Parenta některý další Parent vyhovuje filtru.
        /// </summary>
        internal bool NodeIsVisibleFromParent { get { return (this.Parent != null && (this.Parent.DataRowIsVisibleByFilter || this.Parent.NodeIsVisibleFromParent)); } }
        /// <summary>
        /// Tento řádek má být zobrazen, protože některý z jeho Childs je vyhovuje filtru, anebo některý z jeho Grand-Childs vyhovuje filtru.
        /// </summary>
        internal bool NodeIsVisibleFromChild
        {
            get
            {
                // Hodnota z cache:
                if (!this._NodeIsVisibleFromChild.HasValue)
                {   // Cache je neplatná:
                    if (this.HasChilds)
                        // Tady se může rozběhnout pěkná rekurze (this.Childs[].NodeIsVisibleFromChild) :
                        this._NodeIsVisibleFromChild = this.Childs.Exists(c => (c.DataRowIsVisibleByFilter || c.NodeIsVisibleFromChild));
                    else
                        this._NodeIsVisibleFromChild = false;
                }
                return this._NodeIsVisibleFromChild.Value;
            }
        }
        /// <summary>
        /// Hodnota (text), podle které probíhá třídění v compareru 
        /// </summary>
        internal string SortValue { get; set; }
        /// <summary>
        /// Připraví do všech položek daného soupisu, do jejich property SortValue text odpovídající sloupci (sortColumnIndex)
        /// </summary>
        /// <param name="nodes"></param>
        /// <param name="sortColumnIndex"></param>
        internal static void PrepareSortValue(IEnumerable<GridNodeRowCls> nodes, int sortColumnIndex)
        {
            foreach (GridNodeRowCls node in nodes)
                node.PrepareSortValue(sortColumnIndex);
        }
        /// <summary>
        /// Připraví do this.SortValue text odpovídající sloupci (sortColumnIndex)
        /// </summary>
        /// <param name="sortColumnIndex"></param>
        internal void PrepareSortValue(int sortColumnIndex)
        {
            switch (sortColumnIndex)
            {
                case 0:
                    this.SortValue = this.DataRow.RowItem.Refer;
                    break;
                case 1:
                    this.SortValue = this.DataRow.RowItem.Name;
                    break;
                default:
                    this.SortValue = null;
                    break;
            }
        }
        /// <summary>
        /// Setřídí daný list podle daného třídění. Třídící hodnota (sloupec určující Refer / Name) byl již aplikován, pomocí statické metody GridNodeRowCls.PrepareSortValue()
        /// </summary>
        /// <param name="rowList"></param>
        /// <param name="columnSortMode"></param>
        internal static void SortList(List<GridNodeRowCls> rowList, ColumnSortMode columnSortMode)
        {
            if (rowList != null && rowList.Count > 1)
            {
                switch (columnSortMode)
                {
                    case ColumnSortMode.Ascending:
                        rowList.Sort(GridNodeRowCls.CompareBySortValueAsc);
                        break;
                    case ColumnSortMode.Descending:
                        rowList.Sort(GridNodeRowCls.CompareBySortValueDesc);
                        break;
                }
            }
        }
        /// <summary>
        /// Porovná vzestupně hodnotu SortValue dvou dodaných objektů třídy GridNodeRowCls a vrací výsledek.
        /// </summary>
        /// <param name="nodeA"></param>
        /// <param name="nodeB"></param>
        /// <returns></returns>
        internal static int CompareBySortValueAsc(GridNodeRowCls nodeA, GridNodeRowCls nodeB)
        {
            string textA = (nodeA == null ? null : nodeA.SortValue);
            string textB = (nodeB == null ? null : nodeB.SortValue);
            if (textA == null && textB == null) return 0;
            if (textA == null) return -1;
            if (textB == null) return 1;
            return String.Compare(textA, textB, StringComparison.CurrentCultureIgnoreCase);
        }
        /// <summary>
        /// Porovná sestupně hodnotu SortValue dvou dodaných objektů třídy GridNodeRowCls a vrací výsledek.
        /// </summary>
        /// <param name="nodeA"></param>
        /// <param name="nodeB"></param>
        /// <returns></returns>
        internal static int CompareBySortValueDesc(GridNodeRowCls nodeA, GridNodeRowCls nodeB)
        {
            return CompareBySortValueAsc(nodeB, nodeA);
        }
        #region Cachování příznaku, že některý SubNode je viditelný (NodeIsVisibleFromChild)
        /// <summary>
        /// Sumární příznak viditelnosti subnodes (NodeIsVisibleFromChild)
        /// </summary>
        private bool? _NodeIsVisibleFromChild;
        /// <summary>
        /// Invaliduje interní cache, kde je uložen sumární příznak viditelnosti subnodes (NodeIsVisibleFromChild)
        /// </summary>
        internal void NodeIsVisibleFromChildInvalidate()
        {
            this._NodeIsVisibleFromChild = null;
        }
        #endregion
        #endregion
        #region INTERNAL METODY
        /// <summary>
		/// Metoda zjistí, zda ve svých Childs obsahuje záznam (daný GID)
		/// </summary>
		/// <param name="gId">GID, na který se ptáme</param>
		/// <returns></returns>
		internal bool ContainChild(GID gId)
		{
			return this._ExistsChild(gId);
		}
		/// <summary>
		/// Přidá daný ID do soupisu mých Child (děti, podřízené prvky).
		/// Jeden řádek může mít více childs.
		/// Je to dané konstrukcí dat: jedna poptávka se může vyrábět ve více krocích (strom rozpadu dílce).
		/// Každý podřízený prvek je jeden vyráběný dílec.
		/// </summary>
		/// <param name="child">Prvek Child</param>
		internal void AddChild(GridNodeRowCls child)
		{
            if (!this._ExistsChild(child.GId))
            {
                this.Childs.Add(child);
                this.NodeIsVisibleFromChildInvalidate();
            }
		}
        /// <summary>
        /// Ze svého soupisu Childs odebere prvky pro daný node (podle klíče ID)
        /// </summary>
        /// <param name="node"></param>
        internal void RemoveChild(GridNodeRowCls node)
        {
            this.Childs.RemoveAll(n => n.Id == node.Id);
        }
		/// <summary>
		/// Nastaví si příznaky o načtení subnodů tohoto nodu.
		/// Neřeší jejich soupis, jde jen o příznaky SubNodesLoaded (true) a SubNodesExists(podle parametru).
		/// </summary>
		/// <param name="subNodesExists"></param>
		internal void SetLoadedSubNodes(bool subNodesExists)
		{
			this.SubNodesLoaded = true;
			this.SubNodesExists = subNodesExists;
		}
		/// <summary>
		/// Určí typ ikony přímo před tímto prvkem.
		/// Reaguje na své příznaky.
		/// </summary>
		/// <returns></returns>
		internal TreeLineType GetTreeIcon(TreeRowPosition rowPosition)
		{
			bool isLastRow = (rowPosition == TreeRowPosition.Last || rowPosition == TreeRowPosition.Single);
			bool hasSubNodes = this.SubNodesExists;
			bool subNodesVis = this.SubNodesVisible;
			if (isLastRow)
			{	// Tento node je poslední ve své řadě:
				if (!hasSubNodes) return TreeLineType.Turn;                // Poslední ve své řadě, aktuální, bez subnodes => holá odbočka
				if (subNodesVis && hasSubNodes) return TreeLineType.MinusTurn;   // ..., aktuální, se subnodes, otevřený => MINUS a odbočka
				return TreeLineType.PlusTurn;                              // ..., aktuální, se subnodes, zavřený => PLUS a odbočka
			}
			else
			{	// Tento node není poslední, pod ním jsou další, budeme kreslit nějaký druh pokračovací čáry:
				if (!hasSubNodes) return TreeLineType.Branch;              // Neposlední, aktuální, bez subnodes => čára o větev
				if (subNodesVis && hasSubNodes) return TreeLineType.MinusBranch; // ..., aktuální, se subnodes, otevřený => čára, MINUS a odbočka
				return TreeLineType.PlusBranch;                            // ..., aktuální, se subnodes, zavvřený => čára, PLUS a odbočka
			}
		}
		/// <summary>
		/// Určí typ čáry, která je před ikonami mých subnodů (pokud jsem poslední, tak nic, a pokud nejsem poslední, tak holá čára dolů).
		/// Reaguje na své příznaky.
		/// </summary>
		/// <returns></returns>
		internal TreeLineType GetTreeLine(TreeRowPosition rowPosition)
		{
			if ((rowPosition == TreeRowPosition.Last))
				return TreeLineType.Blank;
			else
				return TreeLineType.Line;
		}
		#endregion
		#region PRIVATE METODY
		/// <summary>
		/// Metoda zjistí, zda ve svých Childs obsahuje záznam (daný GID)
		/// </summary>
		/// <param name="gId">GID, na který se ptáme</param>
		/// <returns></returns>
		private bool _ExistsChild(GID gId)
		{
			return this.Childs.Exists(delegate(GridNodeRowCls c) { return (c.GId == gId); });
		}
		#endregion
		#region EXPAND / COLLAPSE NODU, VČETNĚ CHILDS
		/// <summary>
		/// Rozbalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Expand, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již rozbalený, tak taky změna nenastane).
		/// </summary>
		internal void NodeExpand()
		{
			bool anyNodeIsChanged = false;
			this._NodeExpand(false, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Rozbalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Expand, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již rozbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Expand všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		internal void NodeExpand(bool withChilds)
		{
			bool anyNodeIsChanged = false;
			this._NodeExpand(withChilds, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Rozbalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Expand, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již rozbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Expand všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		internal void NodeExpand(bool withChilds, ref bool anyNodeIsChanged)
		{
			this._NodeExpand(withChilds, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Rozbalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Expand, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již rozbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Expand všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		private void _NodeExpand(bool withChilds, ref bool anyNodeIsChanged)
		{
			// Expandovat this?
			if (!this.SubNodesVisible)
			{
				if (!this.SubNodesExists)
				{	// Pokud neexistují nody, nic nedělám.
				}
				else if (this.SubNodesLoaded)
				{	// Pokud nody existují, a jsou načteny, pak se rozbalím:
					this.SubNodesVisible = true;
					anyNodeIsChanged = true;
				}
				else
				{	// Subnody mají existovat, ale nejsou načteny => co s tím? Zatím nic...
					// Touto cestou nelze rozbalovat node, jehož subnode nejsou načteny.
					// Je třeba použít metodu na úrovni GridDataCls.TreeNodeExpand(), která zajistí donačtení subnodes.
				}
			}

			// Expandovat Childs?
			if (this.HasChilds && withChilds)
			{
				foreach (GridNodeRowCls child in this.Childs)
					child.NodeExpand(withChilds, ref anyNodeIsChanged);
			}
		}
		/// <summary>
		/// Zabalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Collapse, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již sbalený, tak taky změna nenastane).
		/// </summary>
		internal void NodeCollapse()
		{
			bool anyNodeIsChanged = false;
			this._NodeCollapse(false, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Zabalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Collapse, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již sbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Collapse všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		internal void NodeCollapse(bool withChilds)
		{
			bool anyNodeIsChanged = false;
			this._NodeCollapse(withChilds, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Zabalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Collapse, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již sbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Collapse všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		internal void NodeCollapse(bool withChilds, ref bool anyNodeIsChanged)
		{
			this._NodeCollapse(withChilds, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Zabalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Collapse, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již sbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Collapse všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		private void _NodeCollapse(bool withChilds, ref bool anyNodeIsChanged)
		{
			// Sbalit this?
			if (this.SubNodesVisible)
			{
				this.SubNodesVisible = false;
				anyNodeIsChanged = true;
			}

			// Expandovat Childs?
			if (this.HasChilds && withChilds)
			{
				foreach (GridNodeRowCls child in this.Childs)
					child.NodeCollapse(withChilds, ref anyNodeIsChanged);
			}
		}
		#endregion
        #region EXPAND ALL PARENTS
        /// <summary>
        /// Metoda zajistí, že všechny moje Parenty budou otevřené (expandované), takže this node bude zobrazen v tree bez nutnosti rozbalovat parent nody ručně.
        /// </summary>
        internal void ExpandAllParents()
        {
            if (this.HasParent)
            {
                this.Parent.SubNodesVisible = true;      // Zajistím, aby můj parent byl otevřen
                this.Parent.ExpandAllParents();          // Zajistím totéž i pro mého Parenta (rekurzivně), dokud on bude mít svého parenta.
            }
        }
        #endregion
    }
	#endregion
	#region STRUCT RowID : kombinace GID a NodeID, jednoznačná a rychlá identifikace řádku z hlediska vizuálního i datového
	/// <summary>
	/// Struktura RowID : kombinace GID a NodeID, jednoznačná a rychlá identifikace řádku z hlediska vizuálního i datového.
	/// Struktura RowID může být použita jako klíč v Hashtable a Dictionary.
	/// Pracuje s NodeId, což je unikátní klíč (nepracuje se s kombinací NodeId + GID, protože NodeId je dostatečně unikátní).
	/// </summary>
	internal struct RowID
	{
		internal RowID(GID gId, int nodeId)
		{
			this._GId = gId;
			this._NodeId = nodeId;
		}
		private GID _GId;
		private int _NodeId;
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "{NodeId=" + this._NodeId.ToString() + "; GID=" + this._GId.ToString() + "}";
		}
		/// <summary>
		/// GID řádku = dataová identifikace, nezmění se ani po přenačtení grafu. 
		/// Změní se jen připojením k jiným datům nebo po nové přípravě dat.
		/// Pozor: jeden GID (datový řádek) může být zobrazen na více řádcích vizuálních.
		/// Z vizuálního hlediska je GID nedostatečný, neurčuje jeden konkrétní řádek.
		/// </summary>
		internal GID GId { get { return _GId; } }
		/// <summary>
		/// NodeID řádku, je konstantní podobně jako GID. Jednoznačná identifikace vizuálního řádku.
		/// Nemění se při otevírání / zavírání nodů, ani po donačtení dalších podřízených nodů.
		/// Změní se po Clear grafu.
		/// První NodeId je 1, kdežto 0 značí neurčeno.
		/// </summary>
		internal int NodeId { get { return _NodeId; } }
		#region Hashcode, Equals
		/// <summary>
		/// Vrací Hashcode
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode()
		{
			return this._NodeId.GetHashCode();
		}
		/// <summary>
		/// Vrací Equals
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
		public override bool Equals(object obj)
		{
			if (!(obj is RowID)) return false;
			return (RowID._IsEqual(this, (RowID)obj));
		}
		#endregion
		#region operator ==, !=, IComparable)
		/// <summary>
		/// Operátor "je rovno"
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		public static bool operator ==(RowID a, RowID b)
		{
			return RowID._IsEqual(a, b);
		}
		/// <summary>
		/// Operátor "není rovno"
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		public static bool operator !=(RowID a, RowID b)
		{
			return (!RowID._IsEqual(a, b));
		}
		/// <summary>
		/// Porovnání dvou instancí této struktury, zda obsahují shodná data
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		private static bool _IsEqual(RowID a, RowID b)
		{
			bool dataEqual = (a._NodeId == b._NodeId);
			return dataEqual;
		}
		#endregion
	}
	#endregion
	#region CLASS InterGraphCurrentResponseCls : Data, která specifikují reakci this grafu po nějaké akci v source grafu.
	/// <summary>
	/// Data, která specifikují reakci this grafu po nějaké akci v source grafu.
	/// Buď se získávají z datového zdroje thios grafu v requestu DataSourceRequestType.FindInterGraphTargetData, 
	/// anebo se generují v metodách GridDataCls._FindRowPointerList() a _FindElementPointerList().
	/// Generické metody plní pouze pole this.SelectElementList.
	/// </summary>
	internal class InterGraphCurrentResponseCls
	{
		private InterGraphCurrentResponseCls(CurrentGraphCrossActivityMode activityMode)
		{
			this.ActivityMode = activityMode;
			this.ResultRowListChange = RowListChangeMode.None;
			this.ResultItems = null;
			this.ResultActivateRowGId = GID.Empty;
			this.ResultSelectElementList = null;
			this.ResultRowFilterList = null;
			this.ResultInvalidateRowList = null;
			this.ResultElementLinkListChanged = false;
		}
		/// <summary>
		/// Vytvoří prázdnou response. Data je nutno naplnit ručně.
		/// </summary>
		/// <param name="activityMode">Režim aktivity tohoto grafu. Vkládá se do response.ActivityMode.</param>
		/// <returns></returns>
		internal static InterGraphCurrentResponseCls Create(CurrentGraphCrossActivityMode activityMode)
		{
			InterGraphCurrentResponseCls response = new InterGraphCurrentResponseCls(activityMode);
			return response;
		}
		/// <summary>
		/// Vytvoří response z dat z requestu.
		/// </summary>
		/// <param name="activityMode">Režim aktivity tohoto grafu. Vkládá se do response.ActivityMode.</param>
		/// <param name="request"></param>
		/// <returns></returns>
		internal static InterGraphCurrentResponseCls CreateFromRequest(CurrentGraphCrossActivityMode activityMode, DataSourceRequestFindInterGraph request)
		{
			InterGraphCurrentResponseCls response = new InterGraphCurrentResponseCls(request.CurrentActivityMode);
			response.ResultRowListChange = request.ResultRowListChange;
			response.ResultItems = request.ResultItems;
			response.ResultActivateRowGId = request.ResultActivateRowGId;
			response.ResultSelectElementList = request.ResultSelectElementList;
			response.ResultRowFilterList = request.ResultRowFilterList;
			response.ResultInvalidateRowList = request.ResultInvalidateRowList;
			response.ResultElementLinkListChanged = request.ResultElementLinkListChanged;
			return response;
		}
		/// <summary>
		/// Vytvoří odpověď = aktivuj řádek na daném GIDu
		/// </summary>
		/// <param name="activityMode">Režim aktivity tohoto grafu. Vkládá se do response.ActivityMode.</param>
		/// <param name="rowGId"></param>
		/// <returns></returns>
		internal static InterGraphCurrentResponseCls CreateForRow(CurrentGraphCrossActivityMode activityMode, GID rowGId)
		{
			InterGraphCurrentResponseCls response = new InterGraphCurrentResponseCls(activityMode);
			response.ResultActivateRowGId = rowGId;
			return response;
		}
		/// <summary>
		/// Vytvoří odpověď = selectuj dané elementy
		/// </summary>
		/// <param name="activityMode">Režim aktivity tohoto grafu. Vkládá se do response.ActivityMode.</param>
		/// <param name="selectElementList"></param>
		/// <returns></returns>
		internal static InterGraphCurrentResponseCls CreateForElementList(CurrentGraphCrossActivityMode activityMode, List<DataPointerStr> selectElementList)
		{
			InterGraphCurrentResponseCls response = new InterGraphCurrentResponseCls(activityMode);
			response.ResultSelectElementList = selectElementList;
			return response;
		}
		/// <summary>
		/// Režim aktivity current grafu
		/// </summary>
		internal CurrentGraphCrossActivityMode ActivityMode { get; private set; }
		/// <summary>
		/// Režim změny seznamu řádků v tomto grafu
		/// </summary>
		internal RowListChangeMode ResultRowListChange { get; set; }
		/// <summary>
		/// Nově zadané řádky do grafu.
		/// </summary>
		internal List<IDataRow> ResultItems { get; set; }
		/// <summary>
		/// GID řádku, který má být vybrán.
		/// Tento GID, pokud není Empty, má přednost před výsledkem dle this.SelectElementList, protože je konkrétní.
		/// </summary>
		public GID ResultActivateRowGId { get; set; }
		/// <summary>
		/// Index řádku, který se má aktivovat
		/// </summary>
		public int ActiveRowIndex { get; set; }
		/// <summary>
		/// Filtr na řádky v this grafu.
		/// Datový zdroj je může nastavit v requestu DataSourceRequestType.FindInterGraphTargetData do property ResultRowFilterList,
		/// odsud se vždy přenese sem, a odsud pak v reakci v GridDataCls.CrossGraphFindData() do filtru řádků.
		/// Pozor, tento filtr lze zrušit buď tak, že příště se v této property vrátí null, anebo ... - ještě nevím :(  David Janáček.
		/// </summary>
		internal List<GID> ResultRowFilterList { get; set; }
        /// <summary>
        /// Seznam řádků (data), které se mají v tomto grafu zavést (podle pravidel)
        /// </summary>
        internal List<IDataRow> ResultNewDataRowList { get; set; }
        /// <summary>
		/// Seznam řádků (GID), které se mají v tomto grafu invalidovat = znovu načíst jejich obsah (elementy).
		/// </summary>
		internal List<GID> ResultInvalidateRowList { get; set; }
        /// <summary>
        /// Seznam řádků (GID), které se mají v tomto grafu smazat (podle pravidel)
        /// </summary>
        internal List<GID> ResultDeleteRowList { get; set; }
        /// <summary>
		/// Elementy, které mají být vybrané v this grafu.
		/// Systém najde nejnižší index řádku s vybranými elementy, a ten aktivuje.
		/// </summary>
		internal List<DataPointerStr> ResultSelectElementList { get; set; }
		/// <summary>
		/// Příznak, že důsledkem editace došlo ke změnám v seznamu linkovaných elementů.
		/// Poté by měla vizuální vrstva znovu načíst seznam linků elementů (požadavkem typu DataSourceRequestReadRelations.CreateLinkNet())
		/// a zobrazovat jej z tohoto nově získaného seznamu.
		/// </summary>
		internal bool ResultElementLinkListChanged { get; set; }
		/// <summary>
		/// Vrátí true, pokud režim změny řádků (this.ResultRowListChange) obsahuje danou volbu.
		/// Pokud je do tohoto testu zadáno více voleb, vrací true když this.ResultRowListChange obsahuje alespoň jednu z nich.
		/// </summary>
		/// <param name="changeMode"></param>
		/// <returns></returns>
		internal bool IsRowChangeMode(RowListChangeMode changeMode)
		{
			return ((this.ResultRowListChange & changeMode) > 0);
		}
		/// <summary>
		/// Vrátí true, pokud current aktivita (zaznamenaná v response.ActivityMode) podporuje dotazovanou akci (parametr activityTest).
		/// Pokud se testuje více aktivit najednou (hodnota activityTest je složená - jde o Falgs), pak se true vrátí i pro jednu povolenou aktivitu.
		/// Test tedy má charakter "Pokud je alespoň některá z dotazovaných akcí povolená" (testuje se OR: ((this.ActivityMode & activityTest) > 0)).
		/// Pokud je třeba testovat exaktně všechny požadované aktivity, lze využít test IsActiveExactFor().
		/// </summary>
		/// <param name="activityTest"></param>
		/// <returns></returns>
		internal bool IsActiveFor(CurrentGraphCrossActivityMode activityTest)
		{
			return (((int)(this.ActivityMode & activityTest)) > 0);
		}
		/// <summary>
		/// Vrátí true, pokud current aktivita (zaznamenaná v response.ActivityMode) podporuje všechny dotazované akce (parametr activityTest).
		/// Pokud se testuje více aktivit najednou (hodnota activityTest je složená - jde o Falgs), pak se true vrátí jen když this specifikuje všechny dotazované aktivity.
		/// Test tedy má charakter "Pokud jsou všechny dotazované akce povolené" (testuje se AND: ((this.ActivityMode & activityTest) == activityTest)).
		/// Pokud je třeba testovat povolenost kterékoli požadované aktivity, lze využít test IsActiveFor().
		/// </summary>
		/// <param name="activityTest"></param>
		/// <returns></returns>
		internal bool IsActiveExactFor(CurrentGraphCrossActivityMode activityTest)
		{
			return (((CurrentGraphCrossActivityMode)(this.ActivityMode & activityTest)) == activityTest);
		}
	}
	#endregion
	#region DELEGÁTI A EVENTARGS PRO GridDataCls
	/// <summary>
	/// Delegát na metodu, která vrátí element na základě jeho adresy (pointer na řádek a element).
	/// Výkonná metoda je typicky ve třídě GridDataCls.GetElementData()
	/// </summary>
	/// <param name="pointer">Ukazatel na řádek a element</param>
	/// <returns>Data elementu</returns>
	internal delegate GridDataChartElementCls FindElementDataDelegate(DataPointerStr pointer);
	/// <summary>
	/// Předpis delegáta pro event, kterým třída GridDataCls žádá do vizuální vrstvy o překreslení dat
	/// (a/nebo o reload dat).
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	internal delegate void GridDataCallPaintHandler(object sender, GridDataCallPaintEventArgs e);
	/// <summary>
	/// Předpis delegáta pro event, kterým třída GridDataCls oznamuje změnu dat v editoru.
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	internal delegate void EditorDataChangeHandler(object sender, GridDataEventArgs e);
	/// <summary>
	/// Předpis delegáta pro event, kterým třída GridGraphCls oznamuje někam nahoru událost na vizuální nebo datové vrstvě.
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	internal delegate void GridGraphEventHandler(object sender, GridGraphEventArgs e);
	/// <summary>
	/// Předpis delegáta pro event, kterým si třída GridGraphCls žádá o zjištění ticků osy.
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	internal delegate void GridGraphDetectAxisTickHandler(object sender, GridGraphDetectAxisTickArgs e);
	/// <summary>
	/// Základní třída EventArg pro události v grafu
	/// </summary>
	internal class GridEventArgs : EventArgs
	{
		/// <summary>
		/// Druh události
		/// </summary>
		internal GraphEventType EventType { get; private set; }
		/// <summary>
		/// Režim aktivity pro tuto událost, jaké ostatní grafy se mají ovlivňovat
		/// </summary>
		internal TargetGraphCrossActivityMode TargetMode { get; private set; }
		/// <summary>
		/// ID grafu, který akci vyvolává
		/// </summary>
		internal int SourceGraphID { get; private set; }
		/// <summary>
		/// FirstGID grafu, určuje typ grafu (třídu zobrazených dat)
		/// </summary>
		internal GID SourceFirstGId { get; private set; }
		/// <summary>
		/// Režim grafu
		/// </summary>
		internal RowGraphMode SourceGraphMode { get; private set; }
		/// <summary>
		/// ID grafu, který event akceptoval. 
		/// Na počátku obsahuje hodnotu 0, event se postupně předává okolním grafům (kromě vyvolávajícího), včetně tohoto argumentu.
		/// Jakmile některý graf akceptuje událost do té míry, že chce ukončit zpracování této události (nepředávat ji dalším grafům),
		/// pak do této proměnné vloží svoje ID, a zpracování události se tím zakončí.
		/// </summary>
		internal int ResponseAcceptGraphId { get; set; }
		/// <summary>
		/// Příznak, že graf jednak zpracoval mezigrafovou událost, a navíc požaduje, aby byl zobrazen.
		/// Pokud je graf umístěn na TabPage (např. multiplex), pak by měla být aktivována jeho stránka.
		/// </summary>
		internal bool ResponseShowAcceptGraph { get; set; }
		/// <summary>
		/// Příznak, že při této události na grafu jsou povolené akce s řízením aktivní stránky a grafu.
		/// Běžně ano, při změně viditelnosti stránky ne.
		/// Řídí se druhem eventu (true je pro všechny eventy kromě GraphEventType.PageActivate a GraphEventType.None).
		/// </summary>
		internal bool EnabledPageAction
		{
			get
			{
				GraphEventType eventType = this.EventType;
				return (!(
					eventType == GraphEventType.PageSwitch ||
					eventType == GraphEventType.PageActivated ||
					eventType == GraphEventType.PageDeactivated ||
					eventType == GraphEventType.None));
			}
		}
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="graphID">ID grafu</param>
		/// <param name="firstGId">GID první položku, určuje datovou třídu</param>
		/// <param name="graphMode">Default typ grafu</param>
		internal GridEventArgs(int graphID, GID firstGId, RowGraphMode graphMode, GraphEventType eventType, TargetGraphCrossActivityMode targetMode)
		{
			this.EventType = eventType;
			this.TargetMode = targetMode;
			this.SourceGraphID = graphID;
			this.SourceFirstGId = firstGId;
			this.SourceGraphMode = graphMode;
			this.ResponseAcceptGraphId = 0;
			this.ResponseShowAcceptGraph = false;
		}
	}
	/// <summary>
	/// Třída, kterou GridGraphCls předává informace o elementu, na který uživatel kliknul.
	/// </summary>
	internal class GridGraphEventArgs : GridEventArgs
	{
		/// <summary>
		/// Data elementu, který je předmětem eventu
		/// </summary>
		internal IDataElement DataElement { get; set; }
		/// <summary>
		/// Vizuální index řádku, na němž se nachází element. Jednoznačné určení pro řádky, které se vyskytují opakovaně.
		/// </summary>
		internal int RowIndex { get; set; }
		/// <summary>
		/// Data řádku, na němž se nachází element
		/// </summary>
		internal IDataRow DataRow { get; set; }
		/// <summary>
		/// Aktuální stav viditelnosti vztahů v grafu, který event vyvolal
		/// </summary>
		internal bool RelationVisible { get; set; }
		/// <summary>
		/// GID vstupního řádku
		/// </summary>
		internal GID SourceRowGId { get { return (this.DataRow == null ? GID.Empty : this.DataRow.GId); } }
		/// <summary>
		/// GID vstupního elementu
		/// </summary>
		internal GID SourceElementGId { get { return (this.DataElement == null ? GID.Empty : this.DataElement.GId); } }
		/// <summary>
		/// Datapointer vstupního řádku a elementu
		/// </summary>
		internal DataPointerStr SourcePointer { get { return new DataPointerStr(this.SourceRowGId, this.SourceElementGId); } }
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="graphID"></param>
		/// <param name="firstGId"></param>
		/// <param name="graphMode"></param>
		/// <param name="eventType"></param>
		/// <param name="activityMode"></param>
		internal GridGraphEventArgs(int graphID, GID firstGId, RowGraphMode graphMode, GraphEventType eventType, TargetGraphCrossActivityMode targetMode)
			: base(graphID, firstGId, graphMode, eventType, targetMode)
		{
			this.RowIndex = -1;
			this.DataRow = null;
			this.DataElement = null;
			this.RelationVisible = false;
		}
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="eventType"></param>
		internal GridGraphEventArgs(GraphEventType eventType)
			: base(0, GID.Empty, RowGraphMode.NoDrawingElements, eventType, TargetGraphCrossActivityMode.Anywhere)
		{
			this.RowIndex = -1;
			this.DataRow = null;
			this.DataElement = null;
			this.RelationVisible = false;
		}
	}
	/// <summary>
	/// Třída argumentů pro handlery událostí, kdy datová vrstva žádá do vizuální vrstvy o překreslení nebo přenačtení dat.
	/// </summary>
	internal class GridDataCallPaintEventArgs : GridEventArgs
	{
		/// <summary>
		/// Seznam indexů řádků, jichž se event týká. Nebo null, když nejde o seznam řádků.
		/// </summary>
		internal List<int> RowIndexList { get; private set; }
		/// <summary>
		/// Příznak toho, že došlo ke změnám v linkovaných elementech.
		/// </summary>
		internal bool LinkListChanged { get; private set; }
        /// <summary>
        /// Je požadováno překreslení všech řádků grafu
        /// </summary>
        internal bool RowListChanged { get; private set; }
        internal GridDataCallPaintEventArgs(int graphID, GID firstGId, RowGraphMode graphMode, List<int> rowIndexList, bool linkListChanged, bool rowListChanged)
			: base(graphID, firstGId, graphMode, GraphEventType.None, TargetGraphCrossActivityMode.None)
		{
			this.RowIndexList = rowIndexList;
			this.LinkListChanged = linkListChanged;
            this.RowListChanged = rowListChanged;
		}
	}
	/// <summary>
	/// Třída argumentů pro handlery událostí, kdy se v editoru mění data
	/// </summary>
	internal class GridDataEventArgs : GridEventArgs
	{
		/// <summary>
		/// Editor včetně dat, popisujících změnu
		/// </summary>
		internal GridDataEditorCls DataEditor { get; private set; }
		/// <summary>
		/// Typ změny, která probíhá v editoru
		/// </summary>
		internal GridEditingChangeMode ChangeMode { get; private set; }
		/// <summary>
		/// Soupis nově přidaných řádků
		/// </summary>
        internal List<IDataRow> NewDataRows { get; private set; }
        /// <summary>
        /// Soupis invalidovaných řádků
        /// </summary>
        internal List<GID> ChangedRows { get; private set; }
        /// <summary>
        /// Soupis invalidovaných řádků
        /// </summary>
        internal List<GID> DeletedRows { get; private set; }
        /// <summary>
		/// Příznak toho, že došlo ke změnám v linkovaných elementech.
		/// </summary>
		internal bool LinkListChanged { get; private set; }
		/// <summary>
		/// Konstruktor EventArgs editoru
		/// </summary>
		/// <param name="graphID">ID grafu</param>
		/// <param name="firstGId">GID první položky, určuje datovou třídu</param>
		/// <param name="graphMode">Default typ grafu</param>
		/// <param name="dataEditor">Data v editoru</param>
		/// <param name="changeMode">Typ změny (druh editace, ne stav editace)</param>
		internal GridDataEventArgs(int graphID, GID firstGId, RowGraphMode graphMode, GridDataEditorCls dataEditor, GraphEventType eventType, TargetGraphCrossActivityMode targetMode, GridEditingChangeMode changeMode)
			: base(graphID, firstGId, graphMode, eventType, targetMode)
		{
			this.DataEditor = dataEditor;
			this.ChangeMode = changeMode;
		}
		/// <summary>
		/// Konstruktor EventArgs editoru
		/// </summary>
		/// <param name="graphID">ID grafu</param>
		/// <param name="firstGId">GID první položky, určuje datovou třídu</param>
		/// <param name="graphMode">Default typ grafu</param>
		/// <param name="dataEditor">Data v editoru</param>
		/// <param name="changeMode">Typ změny (druh editace, ne stav editace)</param>
		/// <param name="changedRows">Soupis GID invalidovaných řádků</param>
		internal GridDataEventArgs(int graphID, GID firstGId, RowGraphMode graphMode, GridDataEditorCls dataEditor, GraphEventType eventType, TargetGraphCrossActivityMode targetMode, GridEditingChangeMode changeMode, List<GID> changedRows, bool linkListChanged)
			: base(graphID, firstGId, graphMode, eventType, targetMode)
		{
			this.DataEditor = dataEditor;
			this.ChangeMode = changeMode;
			this.ChangedRows = changedRows;
			this.LinkListChanged = linkListChanged;
		}
        /// <summary>
        /// Konstruktor EventArgs editoru
        /// </summary>
        /// <param name="graphID">ID grafu</param>
        /// <param name="firstGId">GID první položky, určuje datovou třídu</param>
        /// <param name="graphMode">Default typ grafu</param>
        /// <param name="dataEditor">Data v editoru</param>
        /// <param name="changeMode">Typ změny (druh editace, ne stav editace)</param>
        /// <param name="changedRows">Soupis GID invalidovaných řádků</param>
        internal GridDataEventArgs(int graphID, GID firstGId, RowGraphMode graphMode, GridDataEditorCls dataEditor, GraphEventType eventType, TargetGraphCrossActivityMode targetMode, GridEditingChangeMode changeMode, 
            List<IDataRow> newDataRows, List<GID> changedRows, List<GID> deletedRows, bool linkListChanged)
            : base(graphID, firstGId, graphMode, eventType, targetMode)
        {
            this.DataEditor = dataEditor;
            this.ChangeMode = changeMode;
            this.NewDataRows = newDataRows;
            this.ChangedRows = changedRows;
            this.DeletedRows = deletedRows;
            this.LinkListChanged = linkListChanged;
        }
    }
	/// <summary>
	/// Třída, přes kterou si GridGraphCls žádá o naplnění ticků osy, aby měl co kreslit.
	/// </summary>
	internal class GridGraphDetectAxisTickArgs : EventArgs
	{
		/// <summary>
		/// Sem prosím naplnit seznam Ticků osy
		/// </summary>
		public List<TimeAxisTickInfo> AxisTicks;
		/// <summary>
		/// Pokud je požadováno vykreslování časových značek na vrchu (nad ostatními objekty), nastavit zde true.
		/// </summary>
		public bool DrawAxisOnTop = false;
	}
	#endregion
}
